package se581.banker;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import se581.banker.dao.AccountQueryDao;
import se581.banker.dao.UserAccountDao;
import se581.banker.manager.AccountTracker;
import se581.banker.manager.AccountTransaction;
import se581.banker.manager.impl.InquiryTracker;
import se581.banker.manager.impl.UpdateTracker;
import se581.banker.processor.Processor;
import se581.banker.proxy.InquiryProxy;
import se581.banker.proxy.UpdateProxy;
import se581.banker.util.OutputFileWriterUtil;

/**
 * Main for Account System Server instance.
 * Many of these can be started.
 * 
 * 
 * @author mmichalak
 *
 */
public class SimulationServerMain {
	static Logger logger = Logger.getLogger(SimulationServerMain.class.getName());
	
	String instanceId = null; // Simple a # to identify an inst running
	String updateInstPortNum = null;
	String inquiryInstPortNum = null;;
	String registrationHost = null;
	String registrationPortNum = null;
	
	SimulationEventProxy simulationEventProxy = null;
	Processor proc1 = null;
	
	UpdateProxy updateProxy = null;
	InquiryProxy inquiryProxy = null;
	
	
	/**
	 * 
	 * @param args 
	 * arg1 = Server Instance Number 
	 * arg2 = UpdateTracker port
	 * arg3 = InquiryTracker port
	 */
	public static void main(String[] args) {
		logger.debug("Starting Simulation Server Instance " + args[0]);	
		
		SimulationServerMain serverInst = new SimulationServerMain();
		
		if (args.length < 3)
			logger.error("Required param is missing.  Required:  instance_id update_server_port inquiry_server_port registration_host:registration_port");
		serverInst.instanceId = args[0];
		serverInst.updateInstPortNum = args[1];
		serverInst.inquiryInstPortNum = args[2];
		
		String[] registrationServerHostPort = args[3].split(":");
		serverInst.registrationHost = registrationServerHostPort[0];
		serverInst.registrationPortNum = registrationServerHostPort[1];
		
		serverInst.setupSimulation();
		
		
		try {
			Thread thrUpdateTracker = new Thread( serverInst.proc1.getUdpateTracker() );
			thrUpdateTracker.setDaemon(true);
			thrUpdateTracker.start();
		} catch (NumberFormatException e) {
			logger.error("Illegal port number:  " + serverInst.updateInstPortNum);
			e.printStackTrace();
		} 
		
		try {	        
			Thread thrInquiryTracker = new Thread( serverInst.proc1.getInquiryTracker() );
			thrInquiryTracker.setDaemon(true);
			thrInquiryTracker.start();

		} catch (NumberFormatException e) {
			logger.error("Illegal port number:  " + serverInst.inquiryInstPortNum);
			e.printStackTrace();
		} 
		
		boolean isRegisterSuccess = serverInst.registerServer();
		if (!isRegisterSuccess) {
			logger.error("Unable to Register this server instance.  Server is not started.");
			return;
		}
		
		// Sleep Loop to keep server alive
		 try {
		        while (true) {
		            Thread.sleep(5 * 1000);

		        }
		 } catch (InterruptedException e) {
		        e.printStackTrace();
		 }

		logger.debug("Ending AccountSimulation Instance " + serverInst.instanceId);
	}
	
	
	/**
	 * Create all the Server objects and sets relationships
	 */
	public void setupSimulation() {

		// create datastore
		Map<String, ArrayList<AccountTransaction>> dataStore = 
			Collections.synchronizedMap( new HashMap<String, ArrayList<AccountTransaction>>() );
		
		// Setup initial Primary Processor
		proc1 = new Processor("Instance 1");
		proc1.setDataStore(dataStore);
		AccountTracker updateTracker = new UpdateTracker();
		updateTracker.setSocketPort( Integer.parseInt(this.updateInstPortNum) );
		proc1.setUdpateTracker(updateTracker);
		AccountTracker inquiryTracker = new InquiryTracker();
		inquiryTracker.setSocketPort( Integer.parseInt(this.inquiryInstPortNum) );
		proc1.setInquiryTracker(inquiryTracker);
		
		OutputFileWriterUtil filewriter = new OutputFileWriterUtil(BankerConstants.CHECKPOINT_PARTIAL_FILENAME);
//		((UpdateTracker)updateTracker).setPartialFileWriter(filewriter);
		proc1.setPartialFileWriter(filewriter);
		
		UserAccountDao userAcctDao = new UserAccountDao();
		updateTracker.setDao(userAcctDao);
		
		AccountQueryDao queryDao = new AccountQueryDao();
		inquiryTracker.setDao(queryDao);
			
	}
	
	
	public boolean registerServer() {

		Socket clientSocket = null;
		DataOutputStream outToServer = null;
		BufferedReader inFromServer = null;

		String retFromServer;
		try {
			clientSocket = new Socket( this.registrationHost, Integer.parseInt(this.registrationPortNum) );
	
			outToServer =
			     new DataOutputStream(
			     clientSocket.getOutputStream());
			
			inFromServer =
			     new BufferedReader(
			     new InputStreamReader(
			     clientSocket.getInputStream()));
  
		} catch (Exception e) {
			logger.error("Error opening client socket to Registration Server port " + this.registrationPortNum);
			//e.printStackTrace(); // May comment out at some point
			
			// Close socket in case still open (probably not...)
			try { clientSocket.close(); } catch (Exception ex) {
				logger.error("Error closing socket in finally:  " + ex.getMessage() );
			}
			
			return false;
		}
		
		
		try {
			//TODO - dyn gen the servers ip
			outToServer.writeBytes("SERVER," + "localhost" + ":" + this.updateInstPortNum + ":" + this.inquiryInstPortNum + "\n" );
			retFromServer = inFromServer.readLine();
			logger.debug("SERVER REGISTRATION RESPONSE: " + retFromServer);
		} catch (Exception e) {
			logger.error("ERROR registering server with Registration Service.");
			e.printStackTrace();
			
			// Close socket in case still open (probably not...)
			try { clientSocket.close(); } catch (Exception ex) {
				logger.error("Error closing socket in finally:  " + ex.getMessage() );
			}
			
			return false;
		}
		
		return true;
	}
	
}
